﻿using System;
using System.Collections.Generic;
using System.Configuration;
using Autofac;
using Autofac.Core;
using Orchard.Data.Providers;
using Orchard.Environment;
using Orchard.Environment.Configuration;
using Orchard.Environment.ShellBuilders.Models;
using Orchard.FileSystems.AppData;
using System.Runtime.Serialization;
using System.Runtime.Serialization.Formatters.Binary;
using System.IO;

namespace Orchard.Data
{
    public static class ContainerExtension
    {
        public static void RegisteDbSession(this ContainerBuilder builder, string configName)
        {
            builder.RegisterType<ShellSettings>()
               .Named<ShellSettings>(configName)
               .WithParameters(new[] {
                    new ResolvedParameter((p,c) => p.Name == "settings",(p,c) => ResetShellSettings(c,configName))
                });

            builder.RegisterType<SessionConfigurationCache>()
                .Named<ISessionConfigurationCache>(configName)
                .WithParameters(new[] {
                    new ResolvedParameter((p,c) => p.Name == "shellSettings",(p,c) => c.ResolveNamed<ShellSettings>(configName)),
                    new ResolvedParameter((p,c) => p.Name == "shellBlueprint",(p,c) => ResetShellBlueprint(c,configName)),
                    new ResolvedParameter((p,c) => p.Name == "appDataFolder",(p,c) => c.Resolve<IAppDataFolder>()), 
                    new ResolvedParameter((p,c) => p.Name == "hostEnvironment",(p,c) => c.Resolve<IHostEnvironment>()), 
                    new ResolvedParameter((p,c) => p.Name == "configurers",(p,c) => c.Resolve<IEnumerable<ISessionConfigurationEvents>>()), 
                });
            builder.RegisterType<SessionFactoryHolder>()
                .Named<ISessionFactoryHolder>(configName)
                .WithParameters(new[] {
                    new ResolvedParameter((p,c) => p.Name == "shellSettings",(p,c) => c.ResolveNamed<ShellSettings>(configName)),
                    new ResolvedParameter((p,c) => p.Name == "shellBlueprint",(p,c) => ResetShellBlueprint(c,configName)),
                    new ResolvedParameter((p,c) => p.Name == "dataServicesProviderFactory",(p,c) => c.Resolve<IDataServicesProviderFactory>()), 
                    new ResolvedParameter((p,c) => p.Name == "appDataFolder",(p,c) => c.Resolve<IAppDataFolder>()), 
                    new ResolvedParameter((p,c) => p.Name == "sessionConfigurationCache",(p,c) => c.ResolveNamed<ISessionConfigurationCache>(configName)), 
                    new ResolvedParameter((p,c) => p.Name == "hostEnvironment",(p,c) => c.Resolve<IHostEnvironment>()), 
                    new ResolvedParameter((p,c) => p.Name == "cacheConfiguration",(p,c) => c.Resolve<IDatabaseCacheConfiguration>()), 
                    new ResolvedParameter((p,c) => p.Name == "configurers",(p,c) => c.Resolve<Func<IEnumerable<ISessionConfigurationEvents>>>())
                });

            builder.RegisterType<SessionLocator>()
                .Named<ISessionLocator>(configName)
                .WithParameters(new[] {
                    new ResolvedParameter((p,c) => p.Name == "sessionFactoryHolder",(p,c) => c.ResolveNamed<ISessionFactoryHolder>(configName)),
                    new ResolvedParameter((p,c) => p.Name == "interceptors",(p,c) => c.Resolve<IEnumerable<ISessionInterceptor>>()), 
                });

            builder.RegisterGeneric(typeof(Repository<>))
               .Named(configName, typeof(IRepository<>))
               .WithParameter(ResolvedParameter.ForNamed<ISessionLocator>(configName)).InstancePerDependency();
        }

        private static ShellBlueprint ResetShellBlueprint(IComponentContext componentContext, string configName)
        {
            var shellBlueprint = componentContext.Resolve<ShellBlueprint>();

            shellBlueprint.Settings = componentContext.ResolveNamed<ShellSettings>(configName);

            return shellBlueprint;
        }

        private static ShellSettings ResetShellSettings(IComponentContext componentContext, string configName)
        {
            var shellSettings = componentContext.Resolve<ShellSettings>();
            var newShellSettings = new ShellSettings();
            newShellSettings.Name = configName;
            newShellSettings.DataTablePrefix = shellSettings.DataTablePrefix;
            newShellSettings.RequestUrlHost = shellSettings.RequestUrlHost;
            newShellSettings.RequestUrlPrefix = shellSettings.RequestUrlPrefix;
            newShellSettings.EncryptionAlgorithm = shellSettings.EncryptionAlgorithm;
            newShellSettings.EncryptionKey = shellSettings.EncryptionKey;
            newShellSettings.HashAlgorithm = shellSettings.HashAlgorithm;
            newShellSettings.HashKey = shellSettings.HashKey;
            newShellSettings.State = shellSettings.State;
            newShellSettings.Themes = shellSettings.Themes;
            newShellSettings.DataProvider = ConfigurationManager.ConnectionStrings[configName].ProviderName;
            newShellSettings.DataConnectionString = ConfigurationManager.ConnectionStrings[configName].ConnectionString;
            return newShellSettings;
        }


        private static T Clone<T>(T source)
        {
            if (!typeof(T).IsSerializable)
            {
                throw new ArgumentException("The type must be serializable.", "source");
            }

            // Don't serialize a null object, simply return the default for that object
            if (Object.ReferenceEquals(source, null))
            {
                return default(T);
            }

            IFormatter formatter = new BinaryFormatter();
            Stream stream = new MemoryStream();
            using (stream)
            {
                formatter.Serialize(stream, source);
                stream.Seek(0, SeekOrigin.Begin);
                return (T)formatter.Deserialize(stream);
            }
        }

    }
}